﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using Proggy.Web.Cms.ContentModel;
using Newtonsoft.Json;

namespace Proggy.Web.Cms.PropertyTypes
{
    /// <summary>
    /// Property type which requires config object
    /// </summary>
    public class ConfigurablePropertyTypeBase : PropertyTypeBase
    {
        /// <summary>
        /// The typed config object
        /// </summary>
        /// <typeparam name="TConfigObjectType"></typeparam>
        /// <returns></returns>
        public TConfigObjectType GetConfigObject<TConfigObjectType>() {
            return (TConfigObjectType)this.BaseConfigObject;

        }

        /// <summary>
        /// The base object
        /// </summary>
        [JsonIgnore]
        public object BaseConfigObject { get; set; }

        /// <summary>
        /// Get in "GetIsValid" scenario
        /// </summary>
        /// <typeparam name="TConfigObjectType"></typeparam>
        /// <param name="model"></param>
        /// <param name="vContext"></param>
        /// <param name="attr"></param>
        /// <returns></returns>
        public TConfigObjectType GetConfigObjectInValidationContext<TConfigObjectType>(ValidationContext vContext, ContentModelPropertyTypeAttribute attr) {

            // find model
            var model = vContext.ObjectInstance;

            // get type of model
            var modelType = model.GetType();

            // get properties
            var allPropInfos = modelType.GetProperties();

            // set prop name start
            this.PropertyNameStart = attr.PropertyName; // (vContext.MemberName == null ? vContext.DisplayName : vContext.MemberName);

            // find corresponding property in model
            var correspondingPropInfo = allPropInfos.SingleOrDefault(x => x.Name == this.PropertyNameStart);

            // check
            if (correspondingPropInfo == null) {
                throw new Exception("Property decorated with the attribute of type " + typeof(ContentModelPropertyTypeAttribute).ToString() +
                    " is looking for a property named '" + PropertyNameStart + "' in the object of type " +
                    modelType.ToString() + " but cannot find it. The attribute parameter 'propertyName' may be mis-spelled?");
            }

            // get prop type
            var correspondingPropType = correspondingPropInfo.PropertyType;

            // get attribute
            var propTypeAttr = (PropertyTypeAttribute)correspondingPropType.GetCustomAttributes(typeof(PropertyTypeAttribute), true).First();

            // get config object type
            var configObjectType = propTypeAttr.RequiredConfigObjectType;

            // get bool
            var requiresConfig = configObjectType != null;

            // check if property type inherits from configurable
            if (requiresConfig)
            {
                // get corresponing property with value
                var correspondingPropWithValue = correspondingPropInfo.GetValue(model, null); 

                // get base config object
                var baseConfigProp = allPropInfos.SingleOrDefault(x => x.Name == this.PropertyNameStart + "ConfigObject");

                // start type of config prop
                Type baseConfigPropType = null;

                // check
                if (baseConfigProp != null)
                {
                    baseConfigPropType = baseConfigProp.PropertyType;
                }

                // check
                if (baseConfigProp == null || baseConfigPropType != configObjectType)
                {
                    throw new Exception(
                        string.Format(
                            "Property \"{0}\" of type \"{1}\" requires fully instantiated corresponding read only property named \"{0}ConfigObject\" of type \"{2}\"",
                            this.PropertyNameStart, correspondingPropType, configObjectType.GetType().ToString()
                        )
                        );
                }

                // get property with value
                var baseConfigObj = baseConfigProp.GetValue(model, null);

                // check null
                if (baseConfigObj == null)
                {
                    throw new Exception(
                        string.Format(
                            "Property \"{0}\" of type \"{1}\" requires fully instantiated corresponding read only property named \"{0}ConfigObject\" of type \"{2}\"",
                            this.PropertyNameStart, correspondingPropType, configObjectType.GetType().ToString()
                        ));
                }

                this.BaseConfigObject = baseConfigObj;
                return this.GetConfigObject<TConfigObjectType>();
            }

            else {
                throw new Exception("This property has no config object");
            }

            
        }

        /// <summary>
        /// After config object is set, this function runs
        /// </summary>
        public virtual void OnAfterConfigObjectSetInBackEnd()
        {
            
        }
    }
}
